package com.fxjava.displayanrdate.service;

import com.fxjava.displayanrdate.mapper.IssueDataMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class AnrDataService {

  @Autowired
  private IssueDataMapper issueDataMapper;

  private static final DateTimeFormatter DF = DateTimeFormatter.ofPattern("yyyy-MM-dd");

  private static class FilterCtx {
    String startDate;
    String endDate;
    String modelsCsv;
  }

  /**
   * 内部辅助：根据前端筛选项构建过滤上下文（时间范围/自定义日期/型号）
   * timeRange: week | month | custom
   */
  private FilterCtx buildFilter(String timeRange, String[] customDateRange, String[] models) {
    FilterCtx ctx = new FilterCtx();
    if ("custom".equalsIgnoreCase(timeRange) && customDateRange != null && customDateRange.length == 2) {
      ctx.startDate = emptyToNull(customDateRange[0]);
      ctx.endDate = emptyToNull(customDateRange[1]);
    } else if ("week".equalsIgnoreCase(timeRange)) {
      LocalDate now = LocalDate.now();
      LocalDate monday = now.with(DayOfWeek.MONDAY);
      ctx.startDate = DF.format(monday);
      ctx.endDate = DF.format(now);
    } else if ("month".equalsIgnoreCase(timeRange)) {
      LocalDate now = LocalDate.now();
      LocalDate first = now.withDayOfMonth(1);
      ctx.startDate = DF.format(first);
      ctx.endDate = DF.format(now);
    }
    if (models != null && models.length > 0) {
      ctx.modelsCsv = Arrays.stream(models)
              .filter(Objects::nonNull)
              .map(String::trim)
              .filter(s -> !s.isEmpty())
              .distinct()
              .collect(Collectors.joining(","));
    }
    return ctx;
  }

  private String emptyToNull(String s) {
    return (s == null || s.trim().isEmpty()) ? null : s.trim();
  }

  /**
   * 内部辅助：返回本周一到今天的日期范围（yyyy-MM-dd）
   */
  private String[] currentWeekRange() {
    LocalDate now = LocalDate.now();
    LocalDate monday = now.with(DayOfWeek.MONDAY);
    return new String[]{DF.format(monday), DF.format(now)};
  }

  /**
   * 内部辅助：返回本月初到今天的日期范围（yyyy-MM-dd）
   */
  private String[] currentMonthRange() {
    LocalDate now = LocalDate.now();
    LocalDate first = now.withDayOfMonth(1);
    return new String[]{DF.format(first), DF.format(now)};
  }

  /**
   * 前端功能：KPI 统计卡片数据（本周问题数、本月问题数、修复率、Top 型号等）
   * 对应前端：getKpiData（KpiCards.vue）
   */
  public Map<String, Object> getKpiData(String timeRange, String[] customDateRange, String[] models) {
    Map<String, Object> result = new HashMap<>();
//得到周一到本日期之间的星期几
    String[] week = currentWeekRange();
//查到周一到本日期
    Map<String, Object> weeklyIssues = issueDataMapper.getIssuesCount(week[0], week[1], null);
    result.put("weeklyIssues", weeklyIssues.getOrDefault("issues", 0));

    String[] month = currentMonthRange();
    Map<String, Object> monthlyIssues = issueDataMapper.getIssuesCount(month[0], month[1], null);
    result.put("monthlyIssues", monthlyIssues.getOrDefault("issues", 0));

    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    // 严格按筛选条件：若当前筛选无数据，直接返回空结构（不回退到全量）
    try {
      Map<String, Object> cntStrict = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long cStrict = cntStrict == null || cntStrict.get("issues") == null ? 0 : ((Number) cntStrict.get("issues")).longValue();
      if (cStrict == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}
    // 若当前过滤时间段没有数据，回退为全量
    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}
    try {
      //这块没用，因为数据库中写修复的数据太少了，所以废弃了统计修复率
      Map<String, Object> fixRate = issueDataMapper.getFixRateFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      Object rate = fixRate == null ? null : fixRate.get("fix_rate");
      result.put("fixRate", rate == null ? 0 : rate);
    } catch (Exception e) {
      result.put("fixRate", 0);
    }

    result.put("recurrenceRate", 0);


    // 重新按严格筛选覆盖 KPI（无数据显示 0/无）
    ctx = buildFilter(timeRange, customDateRange, models);
//    查本周或者本月问题
    if ("week".equalsIgnoreCase(timeRange)) {
      Map<String, Object> w = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      result.put("weeklyIssues", w == null || w.get("issues") == null ? 0 : w.get("issues"));
      result.put("monthlyIssues", 0);
    } else if ("month".equalsIgnoreCase(timeRange)) {
      Map<String, Object> m = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      result.put("monthlyIssues", m == null || m.get("issues") == null ? 0 : m.get("issues"));
      result.put("weeklyIssues", 0);
    } else if ("custom".equalsIgnoreCase(timeRange)) {
      Map<String, Object> c = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      Object cnt = (c == null) ? null : c.get("issues");
      Object val = (cnt == null) ? 0 : cnt;
      result.put("weeklyIssues", val);
      result.put("monthlyIssues", val);
    } else {
      result.put("weeklyIssues", 0);
      result.put("monthlyIssues", 0);
    }
//查指定时间范围的最多数量的学习机
    Map<String, Object> topModel2 = issueDataMapper.getTopModelFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    result.put("topModel", (topModel2 == null || topModel2.get("model") == null) ? "无" : String.valueOf(topModel2.get("model")));

    return result;
  }

  /**
   * 严格筛选版本：不做任何时间回退；当无数据时返回空结构，交由前端展示“所选时间无数据”。
   */
  public Map<String, Object> getTrendDataStrict(String timeRange, String[] customDateRange, String[] models) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);

    boolean groupByMonth = "month".equalsIgnoreCase(timeRange);
    if (groupByMonth) {
      List<Map<String, Object>> monthly = issueDataMapper.getTimeSeriesMonthly(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      if (monthly == null || monthly.isEmpty()) {
        result.put("timeSeries", Map.of());
      } else {
        result.put("timeSeries", formatTimeSeriesData(monthly, "month"));
      }
    } else {
      List<Map<String, Object>> daily = issueDataMapper.getTimeSeriesDaily(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      if (daily == null || daily.isEmpty()) {
        result.put("timeSeries", Map.of());
      } else {
        result.put("timeSeries", formatTimeSeriesData(daily, "date"));
      }
    }

    List<Map<String, Object>> stackRaw = issueDataMapper.getModelStackDataFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    if (stackRaw == null || stackRaw.isEmpty()) {
      result.put("modelStack", Map.of());
      result.put("heatmap", Map.of());
    } else {
      result.put("modelStack", formatModelStackData(stackRaw));
      result.put("heatmap", formatHeatmapData(stackRaw));
    }

    return result;
  }

  /**
   * 型号-时间趋势（按月聚合）。
   * 返回：{ legend: [model...], xAxis: [yyyy-MM...], series: [{name, type, data, markPoint}...] }
   */
  /**
   * 前端功能：型号-时间趋势（折线/堆叠，period: month|week）
   * 对应前端：getModelTrend（ModelTimeTrend.vue）
   */
  public Map<String, Object> getModelTrend(String timeRange, String[] customDateRange, String[] models,
                                           String period) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}

    // 按 period 聚合：month（默认）或 week
    List<Map<String, Object>> rows;
    boolean byWeek = "week".equalsIgnoreCase(period);
    if (byWeek) {
      rows = issueDataMapper.getModelWeeklyTrendFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    } else {
      rows = issueDataMapper.getModelMonthlyTrendFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    }

    // 组装：x轴（月）、图例（型号）、series（每个型号一条线，峰值标红）
    SortedSet<String> months = new TreeSet<>();
    SortedSet<String> modelSet = new TreeSet<>();
    Map<String, Map<String, Integer>> table = new HashMap<>();
    for (Map<String, Object> r : rows) {
      String m = String.valueOf(r.get("model"));
      String timeKey = String.valueOf(r.get(byWeek ? "week" : "month"));
      int cnt = ((Number) r.get("count")).intValue();
      months.add(timeKey);
      modelSet.add(m);
      table.computeIfAbsent(m, k -> new HashMap<>()).put(timeKey, cnt);
    }
    List<String> xAxis = new ArrayList<>(months);
    List<String> legend = new ArrayList<>(modelSet);
    List<Map<String, Object>> series = new ArrayList<>();
    for (String modelName : legend) {
      List<Integer> vals = xAxis.stream()
              .map(d -> table.getOrDefault(modelName, Collections.emptyMap()).getOrDefault(d, 0))
              .collect(Collectors.toList());
      Map<String, Object> s = new HashMap<>();
      s.put("name", modelName);
      s.put("type", "line");
      s.put("data", vals);
      s.put("markPoint", Map.of(
              "data", List.of(Map.of("type", "max", "name", "峰值")),
              "itemStyle", Map.of("color", "#d90000")
      ));
      series.add(s);
    }

    result.put("legend", legend);
    result.put("xAxis", xAxis);
    result.put("series", series);
    return result;
  }

  /**
   * 前端功能：问题分类饼图 + 初筛结果饼图 + 修复率仪表盘
   * 对应前端：getCategoryData（CategoryCharts.vue）
   */
  public Map<String, Object> getCategoryData(String timeRange, String[] customDateRange, String[] models) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);

    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}

    List<Map<String, Object>> problemCategoryData = issueDataMapper.getProblemCategoryDataFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    result.put("problemCategory", formatPieChartData(problemCategoryData, "problem_category"));

    // 改为：查询 rd_initial_screening 的前 12 个字符（去左空格），在 Java 中映射三类
    List<String> screeningPrefixes = issueDataMapper.getInitialScreeningPrefixesFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    Map<String, Long> screeningCount = new LinkedHashMap<>();
    screeningCount.put("有ANR日志", 0L);
    screeningCount.put("无ANR日志", 0L);
    screeningCount.put("其他问题", 0L);
    for (String p : screeningPrefixes) {
      String s = (p == null) ? "" : p.trim();
      if (s.startsWith("无ANR")) {
        screeningCount.put("无ANR日志", screeningCount.get("无ANR日志") + 1);
      } else if (s.startsWith("有ANR")) {
        screeningCount.put("有ANR日志", screeningCount.get("有ANR日志") + 1);
      } else {
        screeningCount.put("其他问题", screeningCount.get("其他问题") + 1);
      }
    }
    List<Map<String, Object>> initialScreeningRows = new ArrayList<>();
    for (Map.Entry<String, Long> e : screeningCount.entrySet()) {
      Map<String, Object> row = new HashMap<>();
      row.put("initial_screening", e.getKey());
      row.put("count", e.getValue());
      initialScreeningRows.add(row);
    }
    result.put("initialScreening", formatPieChartData(initialScreeningRows, "initial_screening"));

    Map<String, Object> fix = issueDataMapper.getFixRateFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    double rate = 0.0;
    if (fix != null && fix.get("fix_rate") != null) {
      rate = ((Number) fix.get("fix_rate")).doubleValue();
    }
    result.put("fixRateGauge", createGaugeData(rate));

    return result;
  }

  /**
   * 前端功能：供应商桑基图 + 词云占位
   * 对应前端：getSupplierData（SupplierCharts.vue）
   */
  public Map<String, Object> getSupplierData(String timeRange, String[] customDateRange, String[] models) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    // 若当前过滤时间段没有数据，回退为全量
    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}

    List<Map<String, Object>> supplier = issueDataMapper.getSupplierDataFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    result.put("sankey", createSankeyData(supplier));
    result.put("wordCloud", createWordCloudData());
    return result;
  }

  // 3.1 问题分类占比 - 下钻 detail_desc
  /**
   * 前端功能（下钻）：问题分类扇区点击后展示 detail_desc 分布（饼图）
   * 对应前端：getCategoryDetailData（CategoryCharts.vue 下钻对话框）
   */
  public Map<String, Object> getCategoryDetailData(String timeRange, String[] customDateRange, String[] models,
                                                   String category) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}

    if (category == null || category.trim().isEmpty()) {
      category = "未知";
    }
    List<Map<String, Object>> rows = issueDataMapper.getCategoryDetailDescDataFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv, category);
    // 映射为饼图数据
    List<Map<String, Object>> items = rows.stream()
            .map(item -> Map.of(
                    "name", String.valueOf(item.get("detail")),
                    "value", item.get("count")
            ))
            .collect(Collectors.toList());
    result.put("series", List.of(Map.of(
            "name", category,
            "type", "pie",
            "data", items
    )));
    result.put("category", category);
    return result;
  }

  /**
   * 前端功能：筛选栏型号下拉选项
   * 对应前端：getModelList（FilterBar.vue）
   */
  public List<String> getModelList() {
    return issueDataMapper.getModelList();
  }

  /**
   * 前端功能：型号问题统计 Top5（柱状图 + 概览）
   * 对应前端：getModelProblemStats（ModelProblemStats.vue）
   */
  public Map<String, Object> getModelProblemStats(String timeRange, String[] customDateRange, String[] models) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    try {
      List<Map<String, Object>> modelStats = issueDataMapper.getModelProblemStatsFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      int totalProblems = modelStats.stream().mapToInt(item -> ((Number) item.get("count")).intValue()).sum();
      List<Map<String, Object>> formattedStats = modelStats.stream().map(item -> {
        int count = ((Number) item.get("count")).intValue();
        double percentage = totalProblems > 0 ? (count * 100.0 / totalProblems) : 0;
        boolean isAlert = percentage > 50;
        Map<String, Object> m = new HashMap<>();
        m.put("model", item.get("model"));
        m.put("count", count);
        m.put("percentage", Math.round(percentage * 100.0) / 100.0);
        m.put("isAlert", isAlert);
        return m;
      }).collect(Collectors.toList());
      result.put("status", "success");
      result.put("data", formattedStats);
      result.put("totalProblems", totalProblems);
      result.put("topModel", modelStats.isEmpty() ? "暂无数据" : modelStats.get(0).get("model"));
    } catch (Exception e) {
      result.put("status", "error");
      result.put("message", "获取型号问题统计失败: " + e.getMessage());
    }
    return result;
  }

  /**
   * 内部辅助：将时间序列结果映射为 ECharts 折线图数据结构
   */
  private Map<String, Object> formatTimeSeriesData(List<Map<String, Object>> data, String labelKey) {
    Map<String, Object> result = new HashMap<>();
    List<Object> x = data.stream().map(item -> item.get(labelKey)).collect(Collectors.toList());
    List<Object> y = data.stream().map(item -> item.get("count")).collect(Collectors.toList());
    result.put("xAxis", x);
    result.put("series", List.of(Map.of(
            "name", "问题数量",
            "type", "line",
            "data", y)));
    return result;
  }

  /**
   * 内部辅助：将按型号+日期聚合的明细映射为堆叠柱图数据结构
   */
  private Map<String, Object> formatModelStackData(List<Map<String, Object>> data) {
    SortedSet<String> dates = new TreeSet<>();
    SortedSet<String> models = new TreeSet<>();
    Map<String, Map<String, Integer>> table = new HashMap<>();
    for (Map<String, Object> row : data) {
      String date = String.valueOf(row.get("date"));
      String model = String.valueOf(row.get("model"));
      int count = ((Number) row.get("count")).intValue();
      dates.add(date);
      models.add(model);
      table.computeIfAbsent(model, k -> new HashMap<>()).put(date, count);
    }
    List<String> xAxis = new ArrayList<>(dates);
    List<String> legend = new ArrayList<>(models);
    List<Map<String, Object>> series = new ArrayList<>();
    for (String m : legend) {
      List<Integer> values = xAxis.stream()
              .map(d -> table.getOrDefault(m, Collections.emptyMap()).getOrDefault(d, 0))
              .collect(Collectors.toList());
      series.add(Map.of(
              "name", m,
              "type", "bar",
              "stack", "total",
              "data", values
      ));
    }
    Map<String, Object> res = new HashMap<>();
    res.put("legend", legend);
    res.put("xAxis", xAxis);
    res.put("series", series);
    return res;
  }

  /**
   * 内部辅助：将按型号+日期聚合的明细映射为热力图数据结构
   */
  private Map<String, Object> formatHeatmapData(List<Map<String, Object>> data) {
    SortedSet<String> dates = new TreeSet<>();
    SortedSet<String> models = new TreeSet<>();
    Map<String, Map<String, Integer>> table = new HashMap<>();
    for (Map<String, Object> row : data) {
      String date = String.valueOf(row.get("date"));
      String model = String.valueOf(row.get("model"));
      int count = ((Number) row.get("count")).intValue();
      dates.add(date);
      models.add(model);
      table.computeIfAbsent(model, k -> new HashMap<>()).put(date, count);
    }
    List<String> xAxis = new ArrayList<>(dates);
    List<String> yAxis = new ArrayList<>(models);
    List<List<Integer>> values = new ArrayList<>();
    for (int yi = 0; yi < yAxis.size(); yi++) {
      String m = yAxis.get(yi);
      for (int xi = 0; xi < xAxis.size(); xi++) {
        String d = xAxis.get(xi);
        int v = table.getOrDefault(m, Collections.emptyMap()).getOrDefault(d, 0);
        values.add(List.of(xi, yi, v));
      }
    }
    Map<String, Object> res = new HashMap<>();
    res.put("xAxis", xAxis);
    res.put("yAxis", yAxis);
    res.put("series", List.of(Map.of(
            "name", "问题热力图",
            "type", "heatmap",
            "data", values
    )));
    return res;
  }

  /**
   * 内部辅助：将名值对结果映射为 ECharts 饼图数据结构
   */
  private Map<String, Object> formatPieChartData(List<Map<String, Object>> data, String nameKey) {
    Map<String, Object> result = new HashMap<>();
    List<Map<String, Object>> items = data.stream()
            .map(item -> Map.of(
                    "name", String.valueOf(item.get(nameKey)),
                    "value", item.get("count")
            ))
            .collect(Collectors.toList());
    result.put("series", List.of(Map.of(
            "name", "数据",
            "type", "pie",
            "data", items)));
    return result;
  }

  /**
   * 内部辅助：根据比率生成 ECharts 仪表盘配置
   */
  private Map<String, Object> createGaugeData(double rate) {
    Map<String, Object> result = new HashMap<>();
    result.put("series", List.of(Map.of(
            "name", "修复率",
            "type", "gauge",
            "data", List.of(Map.of("value", Math.round(rate * 1000.0) / 10.0, "name", "修复率"))
    )));
    return result;
  }

  // 3.2 修复率分析：饼图/折线图 + 仪表盘，period: month|week
  /**
   * 预留功能：修复率分析（饼图/时间序列/仪表盘），当前前端未直接使用。
   */
  public Map<String, Object> getFixRateAnalysis(String timeRange, String[] customDateRange, String[] models,
                                                String period) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}

    boolean byWeek = "week".equalsIgnoreCase(period);
    List<Map<String, Object>> rows = byWeek
            ? issueDataMapper.getFixRateSeriesWeekly(ctx.startDate, ctx.endDate, ctx.modelsCsv)
            : issueDataMapper.getFixRateSeriesMonthly(ctx.startDate, ctx.endDate, ctx.modelsCsv);

    // 时间序列：x 轴 period，y 为百分比
    List<String> xAxis = new ArrayList<>();
    List<Double> rateSeries = new ArrayList<>();
    for (Map<String, Object> r : rows) {
      String p = String.valueOf(r.get("period"));
      long fixed = ((Number) r.get("fixed_count")).longValue();
      long total = ((Number) r.get("total_count")).longValue();
      double rate = total > 0 ? (fixed * 100.0 / total) : 0.0;
      xAxis.add(p);
      rateSeries.add(Math.round(rate * 10.0) / 10.0);
    }
    Map<String, Object> timeSeries = new HashMap<>();
    timeSeries.put("xAxis", xAxis);
    timeSeries.put("series", List.of(Map.of(
            "name", "修复率(%)",
            "type", "line",
            "data", rateSeries,
            "markLine", Map.of(
                    "silent", true,
                    "data", List.of(Map.of("yAxis", 80)),
                    "lineStyle", Map.of("color", "#d90000", "type", "dashed")
            )
    )));
    result.put("timeSeries", timeSeries);

    // 饼图：已修复 vs 未修复（总体）
    Map<String, Object> overall = issueDataMapper.getFixRateOverallFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    long fixed = overall == null || overall.get("fixed_count") == null ? 0 : ((Number) overall.get("fixed_count")).longValue();
    long total = overall == null || overall.get("total_count") == null ? 0 : ((Number) overall.get("total_count")).longValue();
    long unfixed = Math.max(0, total - fixed);
    Map<String, Object> pie = new HashMap<>();
    pie.put("series", List.of(Map.of(
            "name", "修复占比",
            "type", "pie",
            "data", List.of(
                    Map.of("name", "已修复", "value", fixed),
                    Map.of("name", "未修复", "value", unfixed)
            )
    )));
    result.put("pie", pie);

    // 仪表盘：总体修复率
    double overallRate = total > 0 ? (fixed * 1.0 / total) : 0.0;
    result.put("gauge", createGaugeData(overallRate));

    return result;
  }

  private Map<String, Object> createSankeyData(List<Map<String, Object>> data) {
    List<Map<String, Object>> nodes = new ArrayList<>();
    nodes.add(Map.of("name", "总计"));
    List<Map<String, Object>> links = new ArrayList<>();
    for (Map<String, Object> row : data) {
      String supplier = String.valueOf(row.get("supplier"));
      Number count = (Number) row.get("count");
      nodes.add(Map.of("name", supplier));
      links.add(Map.of("source", "总计", "target", supplier, "value", count));
    }
    Map<String, Object> res = new HashMap<>();
    res.put("series", List.of(Map.of(
            "name", "供应商流向",
            "type", "sankey",
            "data", nodes,
            "links", links
    )));
    return res;
  }

  /**
   * 内部辅助：词云占位数据（当前前端为占位展示）
   */
  private Map<String, Object> createWordCloudData() {
    Map<String, Object> result = new HashMap<>();
    result.put("series", List.of(Map.of(
            "name", "词云",
            "type", "wordCloud",
            "data", List.of()
    )));
    return result;
  }

  // 3.3 初筛结果与效率：复现率饼图 + 效率直方 + 箱型图（五数）
  /**
   * 前端功能：初筛结果与效率（复现率饼图/效率直方/箱型图）
   * 对应前端：getInitialScreeningEfficiency（InitialScreeningEfficiency.vue）
   */
  public Map<String, Object> getInitialScreeningEfficiency(String timeRange, String[] customDateRange, String[] models) {
    Map<String, Object> result = new HashMap<>();
    FilterCtx ctx = buildFilter(timeRange, customDateRange, models);
    try {
      Map<String, Object> cnt = issueDataMapper.getIssuesCount(ctx.startDate, ctx.endDate, ctx.modelsCsv);
      long c = cnt == null || cnt.get("issues") == null ? 0 : ((Number) cnt.get("issues")).longValue();
      if (c == 0) {
        result.put("problemCategory", Map.of());
        result.put("initialScreening", Map.of());
        result.put("fixRateGauge", Map.of());
        return result;
      }
    } catch (Exception ignored) {}

    // 复现率分类饼图
    List<Map<String, Object>> reproducibility = issueDataMapper.getReproducibilityCountsFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    List<Map<String, Object>> pieItems = reproducibility.stream()
            .map(item -> Map.of(
                    "name", String.valueOf(item.get("type")),
                    "value", item.get("count")
            ))
            .collect(Collectors.toList());
    Map<String, Object> pie = new HashMap<>();
    pie.put("series", List.of(Map.of(
            "name", "复现率",
            "type", "pie",
            "data", pieItems
    )));
    result.put("reproduciblePie", pie);

    // 初筛效率：按天直方数据（x=天数，y=计数）
    List<Map<String, Object>> histRows = issueDataMapper.getDurationHistogramFiltered(ctx.startDate, ctx.endDate, ctx.modelsCsv);
    List<Integer> xAxis = new ArrayList<>();
    List<Long> counts = new ArrayList<>();
    long total = 0;
    for (Map<String, Object> r : histRows) {
      Integer d = r.get("duration") == null ? null : ((Number) r.get("duration")).intValue();
      Long cnt = r.get("count") == null ? 0L : ((Number) r.get("count")).longValue();
      if (d != null) {
        xAxis.add(d);
        counts.add(cnt);
        total += cnt;
      }
    }
    Map<String, Object> histogram = new HashMap<>();
    histogram.put("xAxis", xAxis);
    histogram.put("series", List.of(Map.of(
            "name", "数量",
            "type", "bar",
            "data", counts
    )));
    result.put("histogram", histogram);

    // 箱型图五数：基于直方重构累计分布估算 min/Q1/median/Q3/max
    Map<String, Double> five = estimateFiveNumberFromHistogram(xAxis, counts, total);
    if (five.isEmpty()) {
      result.put("boxplot", Map.of("xAxis", List.of(), "series", List.of()));
    } else {
      List<Double> box = List.of(
              five.get("min"), five.get("q1"), five.get("median"), five.get("q3"), five.get("max"));
      result.put("boxplot", Map.of(
              "xAxis", List.of("效率"),
              "series", List.of(Map.of(
                      "name", "初筛时长(天)",
                      "type", "boxplot",
                      "data", List.of(box)
              ))));
    }

    return result;
  }

  /**
   * 内部辅助：根据直方分布估算五数概括（min, Q1, median, Q3, max）
   */
  private Map<String, Double> estimateFiveNumberFromHistogram(List<Integer> xAxis, List<Long> counts, long total) {
    Map<String, Double> res = new HashMap<>();
    if (xAxis == null || counts == null || xAxis.isEmpty() || counts.isEmpty() || total <= 0) return res;
    int n = xAxis.size();
    // ensure sorted by duration
    List<int[]> pairs = new ArrayList<>();
    for (int i = 0; i < n; i++) {
      pairs.add(new int[]{ xAxis.get(i), counts.get(i).intValue() });
    }
    pairs.sort(Comparator.comparingInt(a -> a[0]));

    long[] cum = new long[n];
    for (int i = 0; i < n; i++) {
      cum[i] = (i == 0 ? 0 : cum[i-1]) + pairs.get(i)[1];
    }

    res.put("min", (double) pairs.get(0)[0]);
    res.put("max", (double) pairs.get(n-1)[0]);
    res.put("median", (double) valueAtQuantile(pairs, cum, total, 0.5));
    res.put("q1", (double) valueAtQuantile(pairs, cum, total, 0.25));
    res.put("q3", (double) valueAtQuantile(pairs, cum, total, 0.75));
    return res;
  }

  /**
   * 内部辅助：最近秩分位数估计（1-indexed nearest-rank）
   */
  private int valueAtQuantile(List<int[]> pairs, long[] cum, long total, double q) {
    if (total <= 0) return 0;
    long rank = (long) Math.floor(q * (total - 1)) + 1; // nearest-rank (1-indexed)
    for (int i = 0; i < pairs.size(); i++) {
      if (cum[i] >= rank) {
        return pairs.get(i)[0];
      }
    }
    return pairs.get(pairs.size()-1)[0];
  }
}
